package com.lagouedu.server;

import com.lagouedu.classloader.SimpleClassLoader;
import com.lagouedu.processor.RequestProcessor;
import com.lagouedu.servlet.HttpServlet;

import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

import javax.tools.JavaCompiler;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;

/**
 * @Author:
 * @Description:TomcatServer启动
 * @Date: Create in 11:36 下午 2020/3/25
 */
public class TomcatServer {

  //<projectName:<url:servlet>>
  private static Map<String, Map<String, HttpServlet>> servletMap = new HashMap<>();

  private static String PROJECT_PATH = "";

  private static String CONFIGURE_NAME = "web.xml";

  private static int port = 8080;

  private static SimpleClassLoader simpleClassLoader = new SimpleClassLoader();

  public TomcatServer() throws DocumentException {

    this.loadServerConfig();
    File rootFile = new File(PROJECT_PATH);
    if (0 == rootFile.listFiles().length) {
      System.err.println("tomcat无可启动的项目");
    }
    //对项目进行初始化
    for (File chileFile : rootFile.listFiles()) {
      if (chileFile.isDirectory()) {
        //根据当前项目名初始化
        servletMap.put(chileFile.getName(), new HashMap<>());
      }
    }
  }

  private  void loadServerConfig() throws DocumentException {

    InputStream resourceAsStream =
            this.getClass().getClassLoader().getResourceAsStream("myserver.xml");
    SAXReader saxReader = new SAXReader();
    Document document = saxReader.read(resourceAsStream);
    Element rootElement = document.getRootElement();
    Element serviceNode = (Element) rootElement.selectSingleNode("//Service");
    Element servletnameElement = (Element) serviceNode.selectSingleNode("Connector");
      //获取port
    String portStr = servletnameElement.attributeValue("port");
    port = (Integer.parseInt(portStr));
    Element hostElement = (Element) rootElement.selectSingleNode("//Host");
    String name = hostElement.attributeValue("name");
    PROJECT_PATH = hostElement.attributeValue("appBase");
  }

  public void start() throws Exception {
    //加载配置文件
    loadConfigServlet(PROJECT_PATH);

    //初始化一个线程池
    ThreadPoolExecutor threadPool = new ThreadPoolExecutor(10, 50, 100L, TimeUnit.SECONDS,
        new ArrayBlockingQueue<Runnable>(50),
        Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());

    System.out.println("=============MiniTomcat inited!=========");
    //1.创建一个Socket服务端监听8080端口
    ServerSocket server = new ServerSocket(port);
    while (true) {
      Socket socket = server.accept();
      RequestProcessor requestProcessor = new RequestProcessor(socket, servletMap);
      threadPool.execute(requestProcessor);
    }
  }

  /**
   * 加载webapp下的工程
   * @param projectPath
   * @throws Exception
   */
  private void loadConfigServlet(String projectPath) throws Exception {

    File rootFile = new File(projectPath);
    //对子目录进行迭代循环
    for (File chileFile : rootFile.listFiles()) {
      if (chileFile.isDirectory()) {
        loadConfigServlet(chileFile.getPath());
      } else {
        //判断是不是web.xml文件
        if (!CONFIGURE_NAME.equals(chileFile.getName())) {
          continue;
        }
        SAXReader reader = new SAXReader();
        FileInputStream fileInputStream = new FileInputStream(chileFile.getPath());
        Document document = reader.read(fileInputStream);
        Element rootElement = document.getRootElement();
        //拿到所有的<servlet>标签
        List<Element> elements = rootElement.selectNodes("//servlet");
        //对标签进行循环
        for (Element ele : elements) {
          //<servlet-name>dispatcher</servlet-name>
          String servletName = ele.selectSingleNode("servlet-name").getText();
          //<servlet-class>com.mysql.jdbc.Driver</servlet-class>
          String servletClass = ele.selectSingleNode("servlet-class").getText();
          //根据servletName拿到对应的mapping
          //<servlet-name>dispatcher</srvlet-name>
          Element servletMapping = (Element) rootElement
              .selectSingleNode("/web-app/servlet-mapping[servlet-name='" + servletName + "']");
          //<url-pattern>/</url-pattern>
          String urlPattern = servletMapping.selectSingleNode("url-pattern").getText();
          //拿到当前项目名
          String projectName = getProjectName(chileFile.getAbsolutePath());
          //获取当前类的绝对路径
          String javaFilePath = PROJECT_PATH + "/" + projectName + "/src/main/java/" + convertToFilePath(servletClass);
          //将当前类编译并加载进入jvm,并返回创建的对象
          Object instance = loadJavaFile(javaFilePath);
          //将对应的对象放入对应的项目中
          servletMap.get(projectName).put(urlPattern, (HttpServlet) instance);
        }
      }
    }
  }

  /**
   * @return java.lang.String
   * @Author yuyz
   * @Description 获取项目名称（当前项目的文件名就是项目名称）
   * @Date 9:55 上午 2020/3/26
   * @Param []
   */
  public String getProjectName(String path) {
    //1.首先将项目中所有的\\转换为/
    String res = convertToFilePath(path);
    String[] split = res.split("/");
    // 第2个就是当前项目的项目名称
    return split[2];
  }

  private String convertToFilePath(String path) {
    String res = "";
    res = path.replace(".", "/");
    res = res.replace("\\", "/");
    res = res.replace("//", "/");
    return res;
  }

  /**
   * @return void
   * @Author yuyz
   * @Description 将java文件编译并加载进入jvm中
   * @Date 10:58 上午 2020/3/26
   * @Param []
   */
  private Object loadJavaFile(String javaFilePath) throws Exception {
    //1.获取文件名
    String fileName = getFileName(javaFilePath);
    String tempName = javaFilePath + ".java";
    //2.创建文件对象
    File javaFile = new File(tempName);
    //3.将java文件输出至target目录
    JavaCompiler javaCompiler = ToolProvider.getSystemJavaCompiler();
    StandardJavaFileManager manager = javaCompiler.getStandardFileManager(null, null, null);
    Iterable iterable = manager.getJavaFileObjects(javaFile);
    //这个可以指定将class文件输出到哪里
    //Iterable options = Arrays.asList("-d", "d:\\");
    JavaCompiler.CompilationTask task = javaCompiler.getTask(null, manager, null, null, null, iterable);
    task.call();
    manager.close();
    //调用findClass方法将对应的字节码文件找出
    //findClass是将字节码加载进入jvm
    Class clazz = simpleClassLoader.findClass(javaFilePath);
    //获取无参构造
    Constructor constructor = clazz.getConstructor();
    return constructor.newInstance();
  }

  private String getFileName(String path) {
    int index = path.lastIndexOf("/");
    String substring = path.substring(index + 1);
    return substring;
  }
}
